# coding=utf8
__author__ = 'lijian'
import socket
import logging
import struct
import time
import threading
import setting
import ipc
import datetime
import config
from protocol.uplink.packet import *
from protocol.uplink import frameFactory
from protocol.uplink import decode

from worker import AFN01
from worker import AFN02
from worker import AFN04
from worker import AFN05
from worker import AFN09
from worker import AFN0A
from worker import AFN0C
from worker import AFN0D
from worker import AFN10

_sock = None
_connected = False
_alive = False

_frameDecoder = decode.FrameDecode()

_mutex = threading.Lock()

con = threading.Condition()

_syncWaitFrame = None

_lastHeartbeat = 0


def connection(address, port):
    global _sock, _connected

    try:
        logging.info("connection %s:%d " % (address, port))
        _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        _sock.settimeout(15)
        _sock.connect((address, port))
        logging.info("connection %s:%d successful,wait login" % (address, port))
        ipc.showBottom("连接主站成功")
        _connected = True
    except Exception as e:
        logging.info("connection %s:%d fail" % (address, port))
        _connected = False

    return _connected


def disconnect():
    _closeSocket()


def login():
    global _sock, _connected, _alive, _lastHeartbeat
    dataUnit = DataUnit(pn=0, fn=1)
    frame = frameFactory.buildInitiativeFrame(2, dataUnit)
    raw = frame.encode()

    logging.info("send to socket:%r" % frame.info())
    logging.info("%r" % byteUtil.formatBytes(raw))
    _sock.send(struct.pack('B' * len(raw), *raw))

    frame = recvFrame()
    if frame:
        logging.info("login server successful")
        ipc.showBottom("登录主站成功")
        _alive = True
        _lastHeartbeat = time.time()
    else:
        ipc.showBottom("登录主站失败")


def heartbeat():
    logging.info("do heartbeat")
    global _lastHeartbeat
    amp = _gprsSignal()
    print("amp is:%d" % (amp))
    dataUnit = DataUnit(pn=0, fn=3, data=[amp])
    frame = frameFactory.buildInitiativeFrame(2, dataUnit)
    raw = frame.encode()

    logging.info("send to socket:%r" % frame.info())
    logging.info("%r" % byteUtil.formatBytes(raw))
    _sock.send(struct.pack('B' * len(raw), *raw))

    frame = recvFrame()
    if frame:
        logging.info("heartbeat succesful")
        n = datetime.datetime.now()
        msg = "心跳成功(%s)" % ( n.strftime("%H:%M:%S") )
        ipc.showBottom(msg)

    _lastHeartbeat = time.time()


def _closeSocket():
    try:
        if _sock:
            _sock.close()
    except:
        pass


def recvFrame():
    global _connected, _alive
    try:
        mdata = _sock.recv(2048)
        if not mdata:
            return None
        else:
            try:
                remain = struct.unpack('B' * len(mdata), mdata)
                logging.info("recv socket:%s", byteUtil.formatBytes(remain))
                while remain != None and len(remain) > 0:
                    frameRaw, remain = _frameDecoder.decode(remain)
                    if frameRaw:
                        address = frameRaw[4:12]
                        # 解出用户数据域
                        decoder = decode.DataRegionDecode(frameRaw[14:-3])
                        dataRegion = decoder.decodeDataRegion()
                        frame = Frame(dataRegion, address)
                        logging.info("recv frame:%r", frame.info())

                        for unit in frame.dataRegion.dataUnits:
                            logging.info("unit fn:%02d pn:%d : %s", unit.FN, unit.PN, byteUtil.formatBytes(unit.raw))

                        return frame
            except Exception as e:
                logging.error("%r", e)
                return None
    except Exception as e:
        if e.message != "timed out":
            logging.error("%r", e)
            _connected = False
            _alive = False
            return None


def isHeartbeatTime():
    now = time.time()
    if now - _lastHeartbeat >= 120:
        return True
    else:
        return False


def _handleFrame(frame):
    global _syncWaitFrame

    afn = frame.dataRegion.AFN
    seq = frame.dataRegion.SEQ

    if _syncWaitFrame is not None:
        if afn.CMD == 0 and seq.Seq == _syncWaitFrame.dataRegion.SEQ.Seq:
            con.acquire()
            con.notifyAll()
            _syncWaitFrame = None
            con.release()

            return

    if afn.CMD == 0:
        return

    module = eval("AFN%02X" % afn.CMD)

    dataRegion = frame.dataRegion
    results = []

    responseType = 'ACK'
    for unit in dataRegion.dataUnits:
        fn = unit.FN
        str = "module.fn_%d" % fn
        func = eval(str)
        if func:
            logging.info("exec AFN:%02x FN:%d" % (afn.CMD, fn))
            value = func(unit)
            results.append(value)
            if not value:
                logging.warn("exec AFN:%02x FN:%d ,return false" % (afn.CMD, fn))
                responseType = "NAK"
                break
            else:
                if not isinstance(value, bool):
                    responseType = "DU"
        else:
            responseType = "NAK"

    if responseType == "ACK":
        response = frameFactory.ACK(frame)
        sendFrame(response)
    elif responseType == "NAK":
        response = frameFactory.NAK(frame)
        sendFrame(response)
    else:
        response = frameFactory.buildPassiveFrame(afn.CMD, results, seq)
        sendFrame(response)


def sendFrame(frame):
    global _alive, _connected
    if not _alive:
        return False
    try:
        _sock.send(frame.pack())
        logging.info("send frame:%r", frame.info())
        logging.info("%s", byteUtil.formatBytes(frame.raw))

        for unit in frame.dataRegion.dataUnits:
            logging.info("unit fn:%02d pn:%d : %s", unit.FN, unit.PN, byteUtil.formatBytes(unit.raw))
    except Exception as e:
        logging.error("%r", e)
        _connected = False
        _alive = False


def syncSend(frame):
    if not _alive:
        return False

    global _syncWaitFrame
    con.acquire()

    _syncWaitFrame = frame
    sendFrame(frame)
    con.wait(15)

    rtn = False
    if _syncWaitFrame is not None:
        _syncWaitFrame = None
    else:
        rtn = True
    con.release()

    return rtn


def keepAlive():
    while True:
        if not _alive:  # 离线
            if not _connected:  # 连接服务器
                connection(config.network["server"]["ip"], config.network["server"]["port"])

            if _connected:  # 登录
                login()

            if not _alive:
                time.sleep(5)
        else:
            if not isHeartbeatTime():
                frame = recvFrame()
                if not frame:
                    continue
                else:
                    # plc.forceIdle()
                    _handleFrame(frame)
            else:
                heartbeat()

                # def startMonitor():
                # thread = threading.Thread(target=keepAlive)
                # thread.setDaemon(True)
                # thread.start()
                # return thread


def _gprsSignal():
    ampFile = open(setting.AMP_FILE, "r")
    if ampFile:
        line = ampFile.readline()
        try:
            amp = int(line)
            return amp
        except:
            return 99
    else:
        return 99